using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using DamienG.Security.Cryptography;
using FibMatrix;
using FibMatrix.Utils;
using FM_Mono;
using FM_Mono.Editor;
using ICSharpCode.SharpZipLib.Zip;
using LZ4;
using Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Video;
using Object = UnityEngine.Object;

namespace Jelly.Editor.AssetBundle
{
	[InitializeOnLoad]
	public static class JellyBundleAssetExporter
	{
		/// <summary>
		///  package = 只打包; HotUpdate = 只打热更; full = 打包 + 热更并上传CDN
		/// </summary>
		[Flags]
		enum PackMode
		{
			None = 0x0,
			HotUpdate = 0x01,
			Package = 0x10,
			Full = 0x11,
		}
		
		/// <summary>
		/// assetNode中id的格式，long是$filename + _ + $path的md5, short是path的md5
		/// </summary>
		enum NameMode
		{
			Short,
			Long
		}

		enum PackageAssetMode
		{
			Split,
			Combine
		}

		enum ForceRebuildMode
		{
			Inherit,
			ForceEnable,
			ForceDisable
		}
		

		private const string ASSET_CONFIG_CACHE_ROOT = "Library/ProjectBuildCache/AssetConfigCache";
		
		public const string PACKAGE_FINAL_EXPORT_ROOT = "Library/ProjectBuildCache/Package";
		public const string HOTUPDATE_FINAL_EXPORT_ROOT = "Library/ProjectBuildCache/Upload";

		private const string BUNDLE_FLATTEN_DIRECTORY = "Bundles";
		private const string BUNDLE_VARIANT_NAME = "unity3d";

		private const string FRAGMENT_FLATTEN_NAME = "BundleFragment";
		
		private const string PACKAGE_FINAL_EXPORT_BUNDLE_ROOT = PACKAGE_FINAL_EXPORT_ROOT + "/" + BUNDLE_FLATTEN_DIRECTORY;
		private const string HOTUPDATE_FINAL_EXPORT_BUNDLE_ROOT = HOTUPDATE_FINAL_EXPORT_ROOT + "/" + BUNDLE_FLATTEN_DIRECTORY;
		
		private const string PACKAGE_FINAL_EXPORT_ASSEMBLY_ROOT = PACKAGE_FINAL_EXPORT_ROOT + "/Assemblies";
		private const string HOTUPDATE_FINAL_EXPORT_ASSEMBLY_ROOT = HOTUPDATE_FINAL_EXPORT_ROOT + "/Assemblies";

		private const string HOTUPDATE_FINAL_EXPORT_PATCH_ROOT = HOTUPDATE_FINAL_EXPORT_ROOT + "/Patches";

		private const string TEMP_EXPORT_HOTUPDATE_BUNDLE_ROOT = "Library/ProjectBuildCache/BundleBuild/CompressedBundles";
		
		private const string TEMP_EXPORT_ASSEMBLIES_ROOT = "Library/ProjectBuildCache/AssembliesBuild";

		private const long MAX_FRAGMENT_SIZE = int.MaxValue;
		
		private static string _packVersion = "1.0.0";
		private static PackMode _packMode = PackMode.Full;
		private static PackageAssetMode _packageAssetMode = PackageAssetMode.Combine;
		private static NameMode _nameMode = NameMode.Long;
		private static string _packEnv = "ONLINE";
		private static bool _encrypt = false;
		private static ForceRebuildMode _forceRebuildMode = ForceRebuildMode.Inherit;
		
		static JellyBundleAssetExporter()
		{
			UnlockReload();
			string commandLineOptions = System.Environment.CommandLine;
#if UNITY_WEBGL
			_packageAssetMode = PackageAssetMode.Split;
#endif

			if (Application.isBatchMode && commandLineOptions.Contains("-packBundles"))
			{
				string[] commands = commandLineOptions.Split(' ');
				foreach (var rawCommand in commands)
				{
					string command = rawCommand.Trim();
					if (command.StartsWith("-packBundles"))
					{
						if (command.StartsWith("-packBundles="))
						{
							command = command.Substring(13);
							string[] packOpts = command.Split(',');
							for (int i = 0, l = packOpts.Length; i < l; i++)
							{
								string packOpt = packOpts[i];
								if (packOpt.StartsWith("ver="))
								{
									_packVersion = packOpt.Replace("ver=", "").Trim();
								}
								else if (packOpt.StartsWith("mode="))
								{
									string mode = packOpt.Replace("mode=", "").Trim();
									switch (mode)
									{
										case "update":
											_packMode = PackMode.HotUpdate;
											break;
										case "package":
											_packMode = PackMode.Package;
											break;
										case "addConfig":
											_packMode = PackMode.None;
											break;
										default:
											_packMode = PackMode.Full;
											break;
									}
								}
								else if (packOpt.StartsWith("env="))
								{
									_packEnv = packOpt.Replace("env=", "").Trim();
								}
								else if (packOpt.StartsWith("encrypt="))
								{
									_encrypt = packOpt.Replace("encrypt=", "").Trim() == "true";
								}
								else if (packOpt.StartsWith("packageAssetMode="))
								{
									string mode = packOpt.Replace("packageAssetMode=", "").Trim();
									switch (mode)
									{
										case "split":
											_packageAssetMode = PackageAssetMode.Split;
											break;
										default:
											_packageAssetMode = PackageAssetMode.Combine;
											break;
									}
								}
								else if (packOpt.StartsWith("nameMode="))
								{
									string mode = packOpt.Replace("nameMode=", "").Trim();
									switch (mode)
									{
										case "short":
											_nameMode = NameMode.Short;
											break;
										default:
											_nameMode = NameMode.Long;
											break;
									}
								}
								else if (packOpt.StartsWith("forceRebuild="))
								{
									string mode = packOpt.Replace("forceRebuild=", "").Trim();
									switch (mode)
									{
										case "false":
											_forceRebuildMode = ForceRebuildMode.ForceDisable;
											break;
										case "true":
											_forceRebuildMode = ForceRebuildMode.ForceEnable;
											break;
										default:
											_forceRebuildMode = ForceRebuildMode.Inherit;
											break;
									}
								}
							}
						}
					}
				}
				Debug.Log("Batch mode!");
				if (_packMode == PackMode.None)
				{
					EditorCoroutineRunner.StartEditorCoroutine(PrepareToAddConfig());
				}
				else
				{
					EditorCoroutineRunner.StartEditorCoroutine (PrepareToPack(Environment.GetEnvironmentVariable("IS_DEBUG") == "true"));		
				}
			}
		}

		static IEnumerator PrepareToPack(bool isDebug)
		{
			DoExportAssetBundle5(isDebug);
			yield break;
		}

		static IEnumerator PrepareToAddConfig()
		{
			string configURL = Environment.GetEnvironmentVariable("ASSET_CONFIG_URL");
			if (string.IsNullOrEmpty(configURL))
			{
				Debug.LogError("AssetConfig URL is null");
				yield break;
			}
			AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionOnPack;
			EditorApplication.LockReloadAssemblies();
			var uwr = UnityWebRequest.Get(configURL);
			var asop = uwr.SendWebRequest();
			while (!asop.isDone)
			{
				yield return null;
			}

			if (!string.IsNullOrEmpty(uwr.error))
			{
				Debug.LogError($"Download AssetConfig form {configURL} failed");
				yield break;
			}

			var data = uwr.downloadHandler.data;
			AssetConfig config = AssetConfigSerializer.DeserializeAssetsConfig(data, false);
			GenerateAssetConfigCacheAndPatch(config, _packEnv, _packMode);
		}

		public static void HandleLog(string message, string stackTrace, LogType type)
		{
			if (type == LogType.Warning)
				System.Console.ForegroundColor = ConsoleColor.Yellow;
			else if (type == LogType.Error)
				System.Console.ForegroundColor = ConsoleColor.Red;
			else
				System.Console.ForegroundColor = ConsoleColor.White;

			System.Console.WriteLine(message);

		}

		private static void UnhandledExceptionOnPack(object obj, UnhandledExceptionEventArgs args)
		{
			Debug.Log("UnhandledExceptionOnPack");
			Debug.LogException(args.ExceptionObject as Exception);
			DoOnFail();
		}

		private static void DoOnFail()
		{
			EditorApplication.UnlockReloadAssemblies();
			EditorUtility.ClearProgressBar();
			AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionOnPack;
			if (Application.isBatchMode)
			{
				EditorApplication.Exit(1);
			}
		}

		[MenuItem("FMFramework/Unlock Reload", false)]
		public static void UnlockReload()
		{
			EditorUtility.ClearProgressBar();
			EditorApplication.UnlockReloadAssemblies();
		}

		[MenuItem("FMFramework/Export Asset Bundle #%&E", false)]
		public static void ExportAssetBundle5()
		{
			_packVersion = "1.0.1";
			DoExportAssetBundle5(EditorUserBuildSettings.development);
		}
		
		private static void DoExportAssetBundle5(bool isDebug)
		{
			AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionOnPack;
			//            MonsterAssetExporter5 exporter = new MonsterAssetExporter5();
			//      Application.RegisterLogCallback(exporter.HandleLog);
			BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
			// string platformName;
			// switch (target)
			// {
			// case BuildTarget.Android:
			// 	platformName = "Android";
			// 	break;
			// case BuildTarget.iOS:
			// 	platformName = "iOS";
			// 	break;
			// default:
			// 	platformName = target.ToString();
			// 	break;
			// }

			Debug.Log ("Start Asset Export " + target);
			EditorCoroutineRunner.StartEditorCoroutine(ExportResource(target, isDebug), exception =>
			{
				if (exception != null)
				{
					Debug.LogError($"Catch ExportResource Exception\n{exception}");
					DoOnFail();
				}
			});
		}

		public static string GetConfigNameWithVersion(string env, string version)
		{
			string configPrefix = env + "_";
			string configNameWithVersion = $"{configPrefix}AssetConfig_{version}{AssetConfig.ASSET_CONFIG_EXT_NAME}";
			return configNameWithVersion;
		}
		
		private static byte[] BuildAssetOffsetFile(Dictionary<string, long> offsetMap)
		{
			byte[] result = null;
			using(MemoryStream ms = new MemoryStream())
			using (BinaryWriter binaryWriter = new BinaryWriter(ms))
			{
				binaryWriter.Write(offsetMap.Count);
				foreach (var iter in offsetMap)
				{
					binaryWriter.Write(iter.Key);
					binaryWriter.Write(iter.Value);
				}
				binaryWriter.Flush();
				result = ms.ToArray();
			}
			return result;
		}
		
		/// <summary>
		/// 初始化资源和依赖关系，同时会扫描用到的材质、收集shader变种
		/// </summary>
		/// <param name="loadablePathList">可加载资源列表</param>
		/// <param name="forceSingleBundleSet">这里的每个资源都应是一个单独的bundle，不会合并到其他bundle</param>
		/// <param name="topLoadablePathFixedDepMap">可加载资源及其对应的依赖，但key中排除掉被其他可加载资源依赖的可加载资源，
		/// 也就是不包含占位符资源，value中仍然包含占位符资源</param>
		/// <param name="shaderAssetPaths">进入shader bundle的资源文件列表</param>
		/// <param name="allAssetFixedDepMap">所有可加载资源及其依赖的依赖列表</param>
		static void InitAssetDependency(out List<string> loadablePathList, out HashSet<string> forceSingleBundleSet, 
				out Dictionary<string, string[]> topLoadablePathFixedDepMap, out HashSet<string> shaderAssetPaths, 
				out Dictionary<string, string[]> allAssetFixedDepMap)
		{
			//得到所有要导出的资源的路径
			loadablePathList = new List<string>(600);
			Dictionary<string, string> sprite2Atlas = new Dictionary<string, string>(600);
			forceSingleBundleSet = new HashSet<string>();
			GetLoadableAssetPath(ref loadablePathList, ref sprite2Atlas, ref forceSingleBundleSet);

			HashSet<string> loadablePathMap = new HashSet<string>(loadablePathList);
			HashSet<string> allAtlasPaths = new HashSet<string>(sprite2Atlas.Values);
			//得到每个Asset的依赖，保存到loadablePathOriginDepMap，得到所依赖的所有Script和Shader，并计算出changeAssets。
			topLoadablePathFixedDepMap = new Dictionary<string, string[]>(600);
			var allAssetOriginalDepMap = new Dictionary<string, List<string>>(600);
			allAssetFixedDepMap = new Dictionary<string, string[]>();
			shaderAssetPaths = new HashSet<string>(); 
			HashSet<string> allValidDepPaths = new HashSet<string>();	// 所有被依赖的资源
			var shaderVariantCollector = new ShaderVariantCollector();

			// 获取原始的依赖
			HashSet<string> tempDependPaths = new HashSet<string>();
			for (int i = 0; i < loadablePathList.Count; ++i)
			{
				string loadableAssetPath = loadablePathList[i];				
				pathParams[0] = loadableAssetPath;
				string[] dependPaths = AssetDatabase.GetDependencies(pathParams);
				tempDependPaths.Clear();
				for (int j = 0; j < dependPaths.Length; ++j)
				{
					string dependPath = dependPaths[j];
					string ext = Path.GetExtension(dependPath);
					if (string.IsNullOrEmpty(ext) && Directory.Exists(dependPath))
						continue;
					// 排除不是单独文件的built-in asset、Script和Shader
					if (dependPath.StartsWith("Assets/") || dependPath.StartsWith("Packages/"))
					{
						if (dependPath.EndsWith(".cs") || dependPath.EndsWith(".js") || dependPath.EndsWith(".dll") ||
						    dependPath.Contains("Assets/Spine/Editor"))
						{
							continue;
						}

						if (dependPath.EndsWith(".shader"))
						{
							shaderAssetPaths.Add(dependPath);
						}  
						else
						{
							if (dependPath.EndsWith(".mat"))
								shaderVariantCollector.OnMaterialScanned(dependPath);
								
							if (dependPath != loadableAssetPath)
							{
								allValidDepPaths.Add(dependPath);
								tempDependPaths.Add(dependPath);
							}
						}
					}
				}

				var origDependPathList = tempDependPaths.ToList();
				// loadablePathOriginDepMap.Add(loadableAssetPath, origDependPathList);
				allAssetOriginalDepMap.Add(loadableAssetPath, origDependPathList);

				float firstProgress = i * 1f / loadablePathList.Count;
				EditorUtility.DisplayProgressBar("1/4 正在初始化依赖", loadableAssetPath, firstProgress);
			}

			// spriteAtlas可能不是直接加载资源，也不被依赖，需要手动加入到依赖关系里
			FlatAssetDependence(null, new List<string>(allAtlasPaths), 
				allValidDepPaths, allAssetOriginalDepMap);
			// 获取可加载资源的依赖的依赖进allAssetOriginalDepMap中
			foreach (var asset in loadablePathList)
			{
				if (allAssetOriginalDepMap.TryGetValue(asset, out var depAssets))
				{
					FlatAssetDependence(asset, depAssets, allValidDepPaths, allAssetOriginalDepMap);
				}
			}

			// 修正依赖
			var fixedDeps = new HashSet<string>();
			foreach (var iter in allAssetOriginalDepMap)
			{
				var asset = iter.Key;
				bool isAtlas = allAtlasPaths.Contains(asset);
				var originalDeps = iter.Value;
				fixedDeps.Clear();
				foreach (var dep in originalDeps)
				{
					// loadableAssetPath不是spriteAtlas，但依赖了spriteAtlas的sprite
					// 把对sprite的依赖，改为对spriteAtlas的
					var depAsset = dep;
					if (!isAtlas && sprite2Atlas.TryGetValue(depAsset, out var atlasAsset))
					{
						// 这个sprite还是直接加载资源的话，sprite也要当作依赖加进去，后面算placeHolder要用
						if (loadablePathMap.Contains(depAsset))
							fixedDeps.Add(depAsset);
						depAsset = atlasAsset;
					}

					if (isAtlas)
					{
						// spriteAtlas里的sprite都算到spriteAtlas里
						foreach (var v in sprite2Atlas)
						{
							if (v.Value == depAsset)
								fixedDeps.Add(v.Key);
						}
					}
					fixedDeps.Add(depAsset);
				}
				allAssetFixedDepMap.Add(asset, fixedDeps.ToArray());
			}

			// loadablePathOriginDepMap 不包含被其他可加载资源依赖的可加载资源
			foreach (var path in loadablePathList)
				if (!allValidDepPaths.Contains(path) && allAssetFixedDepMap.TryGetValue(path, out var deps))
					topLoadablePathFixedDepMap.Add(path, deps);

			shaderVariantCollector.UpdateShaderVariantCollection(true);
			if (File.Exists(AssetBuilderCfg.Instance.svcPath))
			{
				shaderAssetPaths.Add(AssetBuilderCfg.Instance.svcPath);
				// svc同级或子目录的材质跟shader打到一起，开gpu instancing的材质放到这，防止被裁减
				var path = Path.GetDirectoryName(AssetBuilderCfg.Instance.svcPath);
				// 自动创建instancing材质防止被裁减
				shaderVariantCollector.AddInstancingMaterial(path);
				string[] mats = AssetDatabase.FindAssets("t:Material", new[] {path});
				foreach (var v in mats)
					shaderAssetPaths.Add(AssetDatabase.GUIDToAssetPath(v));
			}
		}

		
		private static string[] pathParams = new string[1];
		/// <summary>
		/// 获取主资源的依赖的依赖列表
		/// </summary>
		/// <param name="mainAsset">可以传入null，表示对dependAssets查引用加入到结果</param>
		/// <param name="dependAssets"></param>
		/// <param name="allValidDepPaths"></param>
		/// <param name="allAssetDepMap">结果加入到这里</param>
		static void FlatAssetDependence(string mainAsset, List<string> dependAssets,
			HashSet<string> allValidDepPaths, Dictionary<string, List<string>> allAssetDepMap)
		{
			foreach(var dep in dependAssets)
			{
				if (dep != mainAsset && !allAssetDepMap.ContainsKey(dep))
				{
					pathParams[0] = dep;
					var rawDepAssets = AssetDatabase.GetDependencies(pathParams);
					List<string> fixedDepAssets = new List<string>();
					foreach (var rawDep in rawDepAssets)
					{
						if (allValidDepPaths.Contains(rawDep) && rawDep != dep)
						{
							fixedDepAssets.Add(rawDep);
						}
					}
					allAssetDepMap.Add(dep, fixedDepAssets);
				}
			}
		}


		static void EditorCheckDirectory(string path, bool pathIsFile = true)
		{
			if (string.IsNullOrEmpty(path))
				return;
			if (pathIsFile || File.Exists(path))
				path = Path.GetDirectoryName(path);
			if (!Directory.Exists(path))
				Directory.CreateDirectory(path);
		}

		static void CheckDirectory()
		{
			EditorCheckDirectory(ASSET_CONFIG_CACHE_ROOT, false);

			if (Directory.Exists(HOTUPDATE_FINAL_EXPORT_PATCH_ROOT))
			{
				Directory.Delete(HOTUPDATE_FINAL_EXPORT_PATCH_ROOT, true);
			}
			Directory.CreateDirectory(HOTUPDATE_FINAL_EXPORT_PATCH_ROOT);
			
			if (Directory.Exists(PACKAGE_FINAL_EXPORT_ROOT))
			{
				Directory.Delete(PACKAGE_FINAL_EXPORT_ROOT, true);
			}
			Directory.CreateDirectory(PACKAGE_FINAL_EXPORT_BUNDLE_ROOT);
			
			if (Directory.Exists(HOTUPDATE_FINAL_EXPORT_ROOT))
			{
				Directory.Delete(HOTUPDATE_FINAL_EXPORT_ROOT, true);
			}
			Directory.CreateDirectory(HOTUPDATE_FINAL_EXPORT_BUNDLE_ROOT);
			
			if (!Directory.Exists(TEMP_EXPORT_HOTUPDATE_BUNDLE_ROOT))
			{
				Directory.CreateDirectory(TEMP_EXPORT_HOTUPDATE_BUNDLE_ROOT);
			}
		}
		
		public static List<(int, MethodInfo)> GetHookFunction<T>() where T:Jelly.Editor.AssetBundle.AssetBuilderCfg.CallbackOrderAttribute
		{
			var types = from assembly in AppDomain.CurrentDomain.GetAssemblies()
#if !NET_STANDARD_2_0    
                        where !(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder)
#endif                        
				from type in assembly.GetTypes()
				where type.IsDefined(typeof(AssetBuilderCfg.BuildHookAttribute), false)
				select type;

			List<(int, MethodInfo)> hookFunctions = new List<(int, MethodInfo)>();
			foreach (var type in types)
			{
				foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public
				                                                           | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
				{
					if(method.IsDefined(typeof(T), false))
					{
						T attr = method.GetCustomAttribute<T>();
						hookFunctions.Add((attr.callbackOrder, method));
					}
				}
			}

			if (hookFunctions.Count > 1)
			{
				hookFunctions.Sort((tuple, valueTuple) => valueTuple.Item1 - tuple.Item1);
			}
			return hookFunctions;
		}

		public static IEnumerator ExportResource(BuildTarget platform, bool isDebug)
		{
			Debug.Log ("Start Export Resource");
			CheckDirectory();

			var preBuildHooks = GetHookFunction<AssetBuilderCfg.PreBuildAttribute>();
			foreach (var hookItem in preBuildHooks)
			{
				hookItem.Item2.Invoke(null, new object[] {platform, isDebug});
			}
			// loadablePathFixedDepMap.keys不包含被其他资源依赖的直接可加载资源，loadablePathList里仍然有
			InitAssetDependency(out List<string> loadablePathList, out HashSet<string> forceSingleBundleSet, 
				out Dictionary<string, string[]> topLoadablePathFixedDepMap, out HashSet<string> shaderAssetPaths,
				out Dictionary<string, string[]> allAssetFixedDepMap);
			yield return null;
			
			//构造出原始的依赖图（森林）
			EditorUtility.DisplayProgressBar("2/4 正在构造依赖图", "Init", 0.0f);
			ResGraph graph = new ResGraph(topLoadablePathFixedDepMap, forceSingleBundleSet, allAssetFixedDepMap);
			yield return 0;

			EditorUtility.DisplayProgressBar("2/4 正在构造依赖图", "GroupNode", 0.33f);
			graph.GroupNodes();
			yield return 0;

			//用于导出Asset.bytes的结构，记录所有节点的依赖关系，在加载的时候需要用这个结构来预先加载依赖节点
			EditorUtility.DisplayProgressBar("3/4 正在打包", "CreateAssetNodeMap", 0.0f);
			Dictionary<string, EditorAssetNode> assetMap;
			CreateAssetNodeMap(graph, out assetMap);
			yield return 0;

			DetermineAssetFinalExportPath(assetMap);
			
			//用5.0的方式打包（BuildMap）
			//此map存储了key代表的资源属于哪个bundle的group
			Dictionary<string, string> reverseGroupMap;
			BuildContext context = CreateAssetBundleBuild(graph, shaderAssetPaths, assetMap, out reverseGroupMap);			
			// 可加载资源 映射到 被归属的资源
			Dictionary<string, string> placeHolderMap = new Dictionary<string, string>();
			ProcessPlaceHolderPath(loadablePathList, topLoadablePathFixedDepMap, reverseGroupMap, placeHolderMap);			
			FixAssetBundleLoadPath(context, loadablePathList, placeHolderMap, assetMap);

			EditorApplication.LockReloadAssemblies();
			if ((_packMode & PackMode.Package) != 0)
				DynamicAssetHelper.GenerateDynamicAssets(assetMap, placeHolderMap);

			BuildAssetBundleOptions options = AssetBuilderCfg.Instance.buildABOption;
			if (_forceRebuildMode != ForceRebuildMode.Inherit)
			{
				if (_forceRebuildMode == ForceRebuildMode.ForceEnable)
				{
					options |= BuildAssetBundleOptions.ForceRebuildAssetBundle;
				}
				else
				{
					options &= (~BuildAssetBundleOptions.ForceRebuildAssetBundle);
				}
			}
			//打包一份LZ4压缩的到PlatformResource/CompressedBundles ，以.unity3d结尾，要上传到CDN让客户端动态下载
			if (!ExportBundle(context, TEMP_EXPORT_HOTUPDATE_BUNDLE_ROOT, platform, options))
			{
				DoOnFail();
				yield break;
			}
			yield return 0;

			EditorUtility.DisplayProgressBar("3.5/4 复制文件", "FillNodeInfoAndFlattenDirectory", 0.0f);
			//把压缩后的包重命名为.bytes，并摊平放在PlatformResource/Android/CompressedBundles
			IEnumerator iter = FillNodeInfoAndFlattenDirectory(TEMP_EXPORT_HOTUPDATE_BUNDLE_ROOT, 
				PACKAGE_FINAL_EXPORT_BUNDLE_ROOT, PACKAGE_FINAL_EXPORT_ROOT,
				HOTUPDATE_FINAL_EXPORT_BUNDLE_ROOT, assetMap);
			while (iter.MoveNext())
				yield return 0;
			yield return 0;
			
			AddPlaceHolder(placeHolderMap, assetMap);
			if ((_packMode & PackMode.Package) != 0)
				DynamicAssetHelper.TyrWriteFileListToScript(assetMap);
			
			// 某些代码会被修改，需要刷新
			AssetDatabase.Refresh();
			AssetDatabase.SaveAssets();
			AssetConfig config = null;
			
#if !UNITY_WEBGL
			EditorUtility.DisplayProgressBar("4/4 Building Assemblies..", "", 0);

			List<EditorAssetNode> canUpdateScriptNodes = null;
			try
			{
				canUpdateScriptNodes = CompileGameMainProj(_packMode, isDebug, null);
			}
			catch (Exception e)
			{
				Console.WriteLine(e);
			}
			if (canUpdateScriptNodes == null)
			{
				Debug.LogError("Generate script nodes failed");
				DoOnFail();
				yield break;
			}
			foreach (var node in canUpdateScriptNodes)
			{
				assetMap.Add(node.id, node);
			}
#endif
			
			StringBuilder logSb = new StringBuilder(); 
			foreach (var node in assetMap)
			{
				logSb.Append(node.Value.id);
				logSb.Append('=');
				logSb.AppendLine(node.Value.url);
			}
			File.WriteAllText("Build/fileMap.txt", logSb.ToString());
			
			
			try
			{
				config = EditorAssetNode.CreateAssetConfigFrom(_packVersion, assetMap);
			}
			catch (Exception e)
			{
				Debug.LogException(e);
			}

			if (config == null)
			{
				Debug.LogError("Generate AssetConfig failed");
				DoOnFail();
				yield break;
			}

			GenerateAssetConfigCacheAndPatch(config, _packEnv, _packMode);
			
			var postBuildHooks = GetHookFunction<AssetBuilderCfg.PostBuildAttribute>();
			foreach (var hookItem in postBuildHooks)
			{
				hookItem.Item2.Invoke(null, new object[] {platform, isDebug, config});
			}
			
			EditorUtility.ClearProgressBar();
			Debug.Log("deploy successful");
			// VCSettings.VCEnabled = false;
			EditorApplication.UnlockReloadAssemblies();
			AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionOnPack;
			if (Application.isBatchMode)
			{
				string buildLockFile = Environment.GetEnvironmentVariable("BUILD_LOCK_PATH");
				if (!string.IsNullOrEmpty(buildLockFile) && File.Exists(buildLockFile))
				{
					File.Delete(buildLockFile);
				}

				EditorApplication.Exit(0);
			}
		}

		/// <summary>
		/// 占位符资源添加到资源列表里，以后好生成资源到bundle的映射
		/// </summary>
		/// <param name="placeHolderMap"></param>
		/// <param name="assetMap"></param>
		static void AddPlaceHolder(Dictionary<string, string> placeHolderMap, Dictionary<string, EditorAssetNode> assetMap)
		{
			Debug.Log($"PlaceHolder count:{placeHolderMap.Count}");
			foreach (var pair in placeHolderMap)
			{
				string needLoadPath = pair.Key;
				string groupPath = pair.Value;
				EditorAssetNode placeHolderNode = CreateAssetNode(needLoadPath);
				assetMap.Add(needLoadPath, placeHolderNode);
				// 使用所属组对应的属性，而不是自身
				EditorAssetNode groupNode = assetMap[groupPath];
				placeHolderNode.placeHolder = true;
				placeHolderNode.dynamic = groupNode.dynamic;
				placeHolderNode.crc32 = groupNode.crc32;
				placeHolderNode.md5 = groupNode.md5;
				placeHolderNode.size = groupNode.size;
				placeHolderNode.url = groupNode.url;
				placeHolderNode.compressedSize = groupNode.compressedSize;
				placeHolderNode.dependence = groupNode.dependence;
				placeHolderNode.otherDepend = groupNode.otherDepend;
				// Debug.Log("Create placeHolder node " + placeHolderNode.id);
			}
		}

		static void PatchWork(List<string> versionList, AssetConfig config, string packEnv,
			string configNameWithVersion)
		{
			var patchTempDir = Path.Combine(Application.temporaryCachePath, "PatchWork");
			if (Directory.Exists(patchTempDir))
			{
				Directory.Delete(patchTempDir, true);
			}

			Directory.CreateDirectory(patchTempDir);
			List<AssetNode> patchNodes = new List<AssetNode>();
			ZipStrings.CodePage = Encoding.UTF8.CodePage;
			FastZip zipUtil = new FastZip();
			var patchNum = Mathf.Min(versionList.Count, AssetBuilderCfg.Instance.maxPatchNum);
			const int BUFFER_SIZE = 1024 * 1024;
			byte[] buffer = new byte[BUFFER_SIZE];
			Debug.Log($"Patch num: {patchNum}");
			for (int i = 0; i < patchNum; ++i)
			{
				string versionNow = versionList[i];
				if (AssetBuilderCfg.Instance.usePatchBreakVersion &&
				    versionNow.Equals(AssetBuilderCfg.Instance.patchBreakVersion))
				{
					// 从最新版本开始，到指定版本时停止
					Debug.Log($"break patch at version {versionNow}, Patch Real num: {i}");
					break;
				}
				string lastConfigNameWithVersion = GetConfigNameWithVersion(packEnv, versionList[i]);
				string lastConfigFileName = Path.Combine(ASSET_CONFIG_CACHE_ROOT, lastConfigNameWithVersion);
				if (File.Exists(lastConfigFileName))
				{
					AssetConfig lastConfig = AssetConfigSerializer.DeserializeAssetsConfig(
						File.ReadAllBytes(lastConfigFileName), false,
						JsonUtility.FromJson<AssetConfig>);

					AssetConfig.FindUpdateAssets(lastConfig, config, out var updateOrAddAssets, out var deleteAssets);
					Debug.Log($"============= from {lastConfig.version} to {config.version} =============");
					Debug.Log("============= update or add assets =============");
					StringBuilder sb = new StringBuilder();
					foreach (var asset in updateOrAddAssets)
					{
						sb.AppendLine($"id {asset.id}, url {asset.url}");
					}

					Debug.Log(sb.ToString());

					Debug.Log("============= delete assets =============");
					sb.Clear();
					foreach (var asset in deleteAssets)
					{
						sb.AppendLine($"id {asset.id}, url {asset.url}");
					}

					Debug.Log(sb.ToString());
					sb.Clear();

					if (updateOrAddAssets.Count > 1)
					{
						string fileName = $"Patch{config.version}_{lastConfig.version}.zip";
						string tempOutputFile = Path.Combine(Application.temporaryCachePath, fileName);
						foreach (var asset in updateOrAddAssets)
						{
							string sourcePath =
								ResourceHelper.GetAssetURL(HOTUPDATE_FINAL_EXPORT_ROOT, asset.url, asset.md5,
									false);
							string targetPath = Path.Combine(patchTempDir, asset.url);
							EditorCheckDirectory(targetPath);
							if (!asset.compressed)
							{
								File.Copy(sourcePath, targetPath, true);
							}
							else
							{
								using (var targetFS = File.OpenWrite(targetPath))
								using (var sourceFS = File.OpenRead(sourcePath))
								using (var lz4Stream = new LZ4Stream(sourceFS, LZ4StreamMode.Decompress))
								{
									targetFS.SetLength(0);

									int readBytes = 0;
									while ((readBytes = lz4Stream.Read(buffer, 0, BUFFER_SIZE)) > 0)
									{
										targetFS.Write(buffer, 0, readBytes);
									}

									targetFS.Flush();
								}
							}
						}

						zipUtil.CreateZip(tempOutputFile, patchTempDir, true, "-^\\..*");
						FileInfo fileInfo = new FileInfo(tempOutputFile);
						if (fileInfo.Exists)
						{
							var patchNode = new AssetNode();
							patchNode.crc32 = Crc32.ComputeFile(tempOutputFile);
							patchNode.md5 = CommonUtil.GetFileMD5(tempOutputFile);
							patchNode.id = fileName;
							patchNode.compressedSize = patchNode.size = (ulong)fileInfo.Length;
							patchNode.url = "Patches/" + fileName;
							string targetPatchFilePath = Path.Combine(HOTUPDATE_FINAL_EXPORT_PATCH_ROOT,
								$"Patch{config.version}_{lastConfig.version}_{patchNode.md5}.zip");
							EditorCheckDirectory(targetPatchFilePath);
							Debug.Log("Move " + targetPatchFilePath);
							File.Move(tempOutputFile, targetPatchFilePath);
							patchNodes.Add(patchNode);
						}
						else
						{
							Debug.LogError($"{tempOutputFile} Generate failed");
						}
					}
				}
			}

			if (patchNodes.Count > 0)
			{
				config.patchList = new AssetList
				{
					nodes = patchNodes.ToArray(),
				};
			}


			using (FileStream fs = File.OpenWrite(Path.Combine(HOTUPDATE_FINAL_EXPORT_ROOT, configNameWithVersion)))
			{
				AssetConfigSerializer.SerializeAssetsConfig(fs, true, config, EditorJsonUtility.ToJson);
				fs.Flush();
				fs.Close();
			}
		}

		private static void GenerateAssetConfigCacheAndPatch(AssetConfig config, string packEnv, PackMode packMode)
		{
			string versionListFile = Path.Combine(ASSET_CONFIG_CACHE_ROOT, $"VersionList_{packEnv}.txt");
			List<string> versionList = null;
			if (File.Exists(versionListFile))
			{
				versionList = new List<string>(File.ReadAllLines(versionListFile));
			}
			else
			{
				versionList = new List<string>();
			}
			
			versionList.Sort(AssetConfig.VersionCompare);
			versionList.Reverse();

			int curVersionIndex = versionList.IndexOf(config.version);
			if (curVersionIndex >= 0)
			{
				versionList.RemoveAt(curVersionIndex);
			}

			int versionInsertIndex = 0;
			if (versionList.Count > 0)
			{
				while (versionInsertIndex < versionList.Count && AssetConfig.VersionCompare(versionList[versionInsertIndex], config.version) > 0)
				{
					++versionInsertIndex;
				}
			}

			string configNameWithVersion = GetConfigNameWithVersion(packEnv, config.version);
			byte[] configBytes = null;
			using (MemoryStream configMS = new MemoryStream())
			{
				AssetConfigSerializer.SerializeAssetsConfig(configMS, false, config, EditorJsonUtility.ToJson);
				configBytes = configMS.ToArray();
			}
			
			string configCacheFileName = Path.Combine(ASSET_CONFIG_CACHE_ROOT, configNameWithVersion);
			EditorCheckDirectory(ASSET_CONFIG_CACHE_ROOT, false);
			File.WriteAllBytes(configCacheFileName, configBytes);
			
			if ((packMode & PackMode.Package) != 0)
			{
				File.WriteAllBytes(Path.Combine(PACKAGE_FINAL_EXPORT_ROOT, AssetConfig.ASSET_CONFIG_FILE_NAME), configBytes);
			}

#if !UNITY_WEBGL
			if ((packMode & PackMode.HotUpdate) != 0)
				PatchWork(versionList, config, packEnv, configNameWithVersion);					
#endif
				

			if (versionInsertIndex < versionList.Count)
			{
				versionList.Insert(versionInsertIndex, config.version);
			}
			else
			{
				versionList.Add(config.version);
			}

			File.WriteAllLines(versionListFile, versionList.ToArray());
		}

		/// <summary>
		/// 获取可加载路径下的资源列表等信息
		/// </summary>
		/// <param name="loadablePathList">可加载资源文件路径列表，AssetDatabase的路径</param>
		/// <param name="spriteAtlasMap">工程内全部sprite到spriteAtlas的映射</param>
		/// <param name="forceSingleBundleSet">强制打到单独bundle里的资源，目前是所有font</param>
		static void GetLoadableAssetPath(ref List<string> loadablePathList, 
			ref Dictionary<string, string> spriteAtlasMap, ref HashSet<string> forceSingleBundleSet)
		{
			string findRoot = BundleResourceFacadeImpl.PROJECT_DYNAMIC_RESOURCE_ROOT;
			if (findRoot.EndsWith("/"))
			{
				findRoot = findRoot.Substring(0, findRoot.Length - 1);
			}

			// 改直接用io接口会更快吧
			// 收集loadablePathList
			string[] allAssetsGUID = AssetDatabase.FindAssets("t:object", new []{findRoot});
			HashSet<string> pathSet = new HashSet<string>();
			for (int i = 0; i < allAssetsGUID.Length; ++i)
			{
				string guid = allAssetsGUID[i];
				string path = AssetDatabase.GUIDToAssetPath(guid);
				//5.4版本的api会返回包括文件夹的path
				if (!path.Contains("."))
					continue;
				if (path.StartsWith(BundleResourceFacadeImpl.PROJECT_DYNAMIC_RESOURCE_ROOT) && !path.EndsWith(".js") && 
					!path.EndsWith(".cs") && !path.EndsWith(".dll")/* && !allAssets[i].EndsWith(".shader")*/)
				{
					if (pathSet.Contains(path))
					{
						continue;
					}
					pathSet.Add(path);
					loadablePathList.Add(path);
				}
			}

			// 搜集spriteAtlasMap， 一般没有资源依赖spriteatlas，需要放到可加载目录才能打成单独的bundle
			string[] allSpfGUID = AssetDatabase.FindAssets("t:spriteatlas", new[] {"Assets"});
			for (int i = 0; i < allSpfGUID.Length; ++i)
			{
				string guid = allSpfGUID[i];
				string path = AssetDatabase.GUIDToAssetPath(guid);
				forceSingleBundleSet.Add(path);
				string[] deps = AssetDatabase.GetDependencies(path);

				//检测一个图集内同名sprite
				if (AssetBuilderCfg.Instance.ignoreDuplicatedSpriteNameCheck == false)
				{
					var checkDuplicateNameResult = CheckHasDuplicateName(deps);
					if (checkDuplicateNameResult.Key)
					{
						Debug.LogError($"!!!{path} Contains Duplicate Named Sprites: {checkDuplicateNameResult.Value}");
						//exit
						DoOnFail();
					}
				}

				List<string> dirs = new List<string>();
				foreach (var dep in deps)
				{
					Type type = AssetDatabase.GetMainAssetTypeAtPath(dep);
					if (typeof(Texture).IsAssignableFrom(type))
					{
						spriteAtlasMap[dep] = path;
					}
					else if (type == typeof(UnityEditor.DefaultAsset))
					{
						// 目录，把其下的所有都算到spriteAtlas里。
						// topHero会有.asset文件引用sprite，归到一起能显著嫌少bundle个数
						dirs.Add(dep);
					}
				}

				// bugfix, 空目录会当作根目录处理
				if (dirs.Count > 0)
				{
					string[] assetInDirs = AssetDatabase.FindAssets("t:object", dirs.ToArray());
					foreach (var v in assetInDirs)
					{
						string p = AssetDatabase.GUIDToAssetPath(v);
						spriteAtlasMap[p] = path;
					}
				}
			}

			string[] allFontGUID = AssetDatabase.FindAssets("t:font", new[] {"Assets"});
			for (int i = 0; i < allFontGUID.Length; ++i)
			{
				string guid = allFontGUID[i];
				string path = AssetDatabase.GUIDToAssetPath(guid);
				forceSingleBundleSet.Add(path);
			}
		}

		private static KeyValuePair<bool, string> CheckHasDuplicateName(string[] paths)
		{
			HashSet<string> names = new HashSet<string>();
			foreach (var path in paths)
			{
				if (AssetDatabase.GetMainAssetTypeAtPath(path) == typeof(Texture2D))
				{
					var name = Path.GetFileNameWithoutExtension(path);
					if (names.Contains(name))
					{
						return new KeyValuePair<bool, string>(true, name);
					}
					names.Add(name);
				}
			}
			return new KeyValuePair<bool, string>(false, null);
		}
		
		private static EditorAssetNode CreateAssetNode(string path)
		{
			EditorAssetNode assetNode = new EditorAssetNode();
			assetNode.id = path;
			assetNode.asset = AssetDatabase.LoadMainAssetAtPath(path);
			if(assetNode.asset == null)
			{
				Debug.LogError("assetNode.asset == null :" + path);
			}
			else
			{
				assetNode.assetGUID = AssetDatabase.AssetPathToGUID(path);
			}
			UpdateAssetNodeProperty(assetNode);
			return assetNode;
		}

		/// <summary>
		/// 创建出用于生成AssetConfig的字典
		/// </summary>
		/// <param name="graph">Graph.</param>
		/// <param name="assetMap">Asset map.</param>
		private static void CreateAssetNodeMap(ResGraph graph, out Dictionary<string, EditorAssetNode> assetMap)
		{
			EditorAssetNode assetNode;
			assetMap = new Dictionary<string, EditorAssetNode>();

			assetNode = new EditorAssetNode();
			// shader asset
			assetNode.id = BundleResourceFacadeImpl.PROJECT_DYNAMIC_RESOURCE_ROOT + AssetBuilderCfg.Instance.shaderBundleFilename;
			assetNode.asset = null;
			assetMap.Add(assetNode.id, assetNode);

			Queue<ResNode2> nodeQueue = new Queue<ResNode2>();
			int rootCount = graph.rootNodeList.Count;
			for(int i = 0; i < rootCount; ++i)
			{
				ResNode2 node = graph.rootNodeList[i];
				nodeQueue.Enqueue(node);
			}

			while(nodeQueue.Count > 0)
			{
				ResNode2 node = nodeQueue.Dequeue();
				if (!assetMap.ContainsKey(node.path))
				{
					assetNode = CreateAssetNode(node.path);
					assetMap.Add(node.path, assetNode);
					//                    AssetImporter importer = AssetImporter.GetAtPath(node.path);
					//					if(importer.assetBundleName != node.path)
					//                    	importer.assetBundleName = node.path;
					//                    importer.assetBundleVariant = "hd";
				} else
				{
					assetNode = assetMap [node.path] as EditorAssetNode;
				}

				if (node.dependence != null && node.dependence.Count > 0)
				{
					assetNode.dependence = new List<EditorAssetNode>(node.dependence.Count);
					for (int j = 0; j < node.dependence.Count; ++j)
					{
						var childResNode = node.dependence[j];
						EditorAssetNode childAssetNode;
						if (!assetMap.ContainsKey(childResNode.path))
						{
							childAssetNode = CreateAssetNode(childResNode.path);
							assetMap.Add(childResNode.path, childAssetNode);
						} else
						{
							childAssetNode = assetMap [childResNode.path] as EditorAssetNode;
						}

						if (!assetNode.dependence.Contains(childAssetNode))
						{
							assetNode.dependence.Add(childAssetNode);
						}
						if(childResNode.dependence != null && childResNode.dependence.Count > 0)
						{
							nodeQueue.Enqueue(childResNode);
						}
					}
				}
			}

			if (false)
			{
				StringBuilder sb = new StringBuilder();
				List<string> tempList = new List<string>(assetMap.Keys);
				tempList.Sort();
				var iter = tempList.GetEnumerator();
				while(iter.MoveNext())
					sb.AppendLine(iter.Current);
				Debug.Log(sb);
			}
		}

		/// <summary>
		/// 找到placeHolder占位符资源（被其他可加载资源依赖的可加载资源）及其所属bundle资源
		/// </summary>
		/// <param name="loadablePathList"></param>
		/// <param name="loadablePathOriginDepMap"></param>
		/// <param name="reverseGroupMap"></param>
		/// <param name="placeHolderMap"></param>
		private static void ProcessPlaceHolderPath(List<string> loadablePathList, 
				Dictionary<string, string[]> loadablePathOriginDepMap, 
				Dictionary<string, string> reverseGroupMap, Dictionary<string, string> placeHolderMap)
		{
			foreach (var needLoadPath in loadablePathList)
			{
				if (!loadablePathOriginDepMap.ContainsKey(needLoadPath))
				{
					string groupPath;
					if (reverseGroupMap.TryGetValue(needLoadPath, out groupPath))
					{
						if (groupPath != needLoadPath)
						{
							placeHolderMap.Add(needLoadPath, groupPath);
						}
					}
					else
					{
						Debug.LogError($"Error: {needLoadPath} exist in {BundleResourceFacadeImpl.PROJECT_DYNAMIC_RESOURCE_ROOT} but can't find group node");
					}
				}
			}
			loadablePathOriginDepMap.Clear();
		}

		/// <summary>
		/// 修改一些可加载资源的加载名addressableNames
		/// </summary>
		/// <param name="context"></param>
		/// <param name="loadablePathList"></param>
		/// <param name="placeHolderMap"></param>
		/// <param name="assetMap"></param>
		private static void FixAssetBundleLoadPath(BuildContext context, List<string> loadablePathList, 
				Dictionary<string, string> placeHolderMap, Dictionary<string, EditorAssetNode> assetMap)
		{
			HashSet<string> loadableSet = new HashSet<string>(loadablePathList);
			AssetBundleBuild[] builds = context.bundleBuildList;
			for (int i = 0; i < builds.Length; i++)
			{
				AssetBundleBuild build = builds[i];
				string[] assetNames = build.assetNames;
				string[] addrAssetNames = null;
				for(int index = 0; index < assetNames.Length; ++index)
				{
					var assetName = assetNames[index];
					if (!loadableSet.Contains(assetName))
					{
						continue;
					}
					if (assetMap.TryGetValue(assetName, out var assetNode))
					{
						if (assetNode.id != assetName)
						{
							addrAssetNames = addrAssetNames ?? new string[assetNames.Length];
							addrAssetNames[index] = assetNode.id;
							// Debug.Log($"add addressable name [{assetNode.id}] for {assetName}");
						}
					}
					else if (placeHolderMap.ContainsKey(assetName))
					{
						string newId = TransformAssetId(AssetDatabase.LoadMainAssetAtPath(assetName), assetName);
						if (newId != assetName)
						{
							addrAssetNames = addrAssetNames ?? new string[assetNames.Length];
							addrAssetNames[index] = newId;
							Debug.Log($"add addressable name2 [{newId}] for {assetName}");
						}
					}
				}

				if (addrAssetNames != null)
				{
					build.addressableNames = addrAssetNames;
					builds[i] = build;
				}
			}
		}

		private static string TransformAssetId(Object asset, string id)
		{
			if (asset is Texture)
			{
				//                    Debug.LogWarning("asset is Texture: " + assetNode.id);
				id = id.Remove(id.LastIndexOf('.')) + ".tex";
			} else if (asset is TextAsset)
			{
				if (!id.EndsWith(".bytes"))//二进制文件不做转换，Runtime使用 LoadBytesTextAsset 读取
				{
					id = id.Remove(id.LastIndexOf('.')) + ".txt";
				}
			} else if (asset is AudioClip)
			{
				//                    Debug.LogWarning("asset is AudioClip: " + assetNode.id);
				id = id.Remove(id.LastIndexOf('.')) + ".asset";
			}

			return id;
		}

		/// <summary>
		/// 设置assetNode的id和dynamic字段（改id是为了在ResourceFacade里加载Object时，用DetermineLoadParams来增加后缀以便load正确的文件名）
		/// </summary>
		private static void UpdateAssetNodeProperty(EditorAssetNode assetNode)
		{
			//设置assetNode的id和dynamic字段（改id是为了在ResourceFacade里加载Object时，用DetermineLoadParams来增加后缀以便load正确的文件名）
			if (assetNode.asset != null)
			{
				var origExt = Path.GetExtension(assetNode.id);
				assetNode.id = TransformAssetId(assetNode.asset, assetNode.id);
				// 流式音频或者视频
				if (assetNode.asset is AudioClip && 
				    ((AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(assetNode.asset)) as AudioImporter).defaultSampleSettings.loadType ==
						AudioClipLoadType.Streaming) || 
				     (assetNode.asset is VideoClip))
				{
					assetNode.extension = origExt;
					assetNode.needExportBundle = false;
				}
				assetNode.asset = null;
			}
		}

		class BuildContext
		{
			public AssetBundleBuild[] bundleBuildList;

			public AssetBundleBuild[] rawBuildList;
		}


		/// <summary>
		/// 确定node的bundle文件名，取文件名做前缀，path的md5做后缀，拼接而成
		/// </summary>
		/// <param name="assetMap"></param>
		private static void DetermineAssetFinalExportPath(Dictionary<string, EditorAssetNode> assetMap)
		{
			Dictionary<string, List<EditorAssetNode>> reversePathMap = new Dictionary<string, List<EditorAssetNode>>();
			foreach (var iter in assetMap)
			{
				string fileName = Path.GetFileName(iter.Value.id);
				if (!reversePathMap.ContainsKey(fileName))
				{
					reversePathMap.Add(fileName, new List<EditorAssetNode>(){iter.Value});
				}
				else
				{
					reversePathMap[fileName].Add(iter.Value);
				}
			}

			foreach (var iter in reversePathMap)
			{
				string fileName = Path.GetFileName(iter.Key);
				
				List<EditorAssetNode> nodeList = iter.Value;
				for (int i = 0, l = nodeList.Count; i < l; i++)
				{
					EditorAssetNode node = nodeList[i];
					var idMd5 = CommonUtil.GetBytesMD5(Encoding.UTF8.GetBytes(node.id));
					node.finalExportName = _nameMode == NameMode.Long ? fileName + "(" + idMd5 + ")" : idMd5;
					if (node.finalExportName.Length > 255)
					{
						Debug.LogError($"{node.finalExportName} size overflow");
						DoOnFail();
					}
					else
					{
						// Debug.Log($"{fileName} name modify, {iter.Key} remapped to {node.finalExportName}");
					}
				}
			}
		}

		/// <summary>
		/// 生成unity构建api所需的AssetBundleBuild数组
		/// </summary>
		/// <param name="graph"></param>
		/// <param name="shaderAssetPaths"></param>
		/// <param name="assetNodeMap"></param>
		/// <param name="reverseGroupMap">资源到bundle的映射，用于后续计算占位符资源</param>
		/// <returns></returns>
		private static BuildContext CreateAssetBundleBuild(ResGraph graph, HashSet<string> shaderAssetPaths, 
					Dictionary<string, EditorAssetNode> assetNodeMap, out Dictionary<string, string> reverseGroupMap)
		{
			List<AssetBundleBuild> bundleBuildList = new List<AssetBundleBuild>();
			//5.0新打包方式不能单独打包脚本，所以没有AllComponents.unity3d
			AssetBundleBuild shaderBuild = new AssetBundleBuild();
			shaderBuild.assetBundleName = Path.Combine(BundleResourceFacadeImpl.PROJECT_DYNAMIC_RESOURCE_ROOT, AssetBuilderCfg.Instance.shaderBundleFilename);
			shaderBuild.assetBundleVariant = BUNDLE_VARIANT_NAME;
			shaderBuild.assetNames = shaderAssetPaths.ToArray();
			bundleBuildList.Add(shaderBuild);

			// 建立资源节点到bundle节点的反向索引
			reverseGroupMap = new Dictionary<string, string>();
			foreach (var shaderPath in shaderBuild.assetNames)
				reverseGroupMap.Add(shaderPath, shaderBuild.assetBundleName);
			HashSet<string> groupPathList = new HashSet<string>();
			foreach (var kv in graph.groupedNode)
			{
				groupPathList.Clear();
				ResNode2.GetAllGroupPath(kv.Value, groupPathList);
				foreach (var resPath in groupPathList)
					reverseGroupMap.Add(resPath, kv.Value.path);
			}

			List<AssetBundleBuild> rawBuildList = new List<AssetBundleBuild>();
			var iter = graph.groupedNode.GetEnumerator();
			bool tryFixSameFilenameHashCollision = AssetBuilderCfg.Instance.tryFixSameFilenameHashCollision;
			HashSet<string> assetNamesList = new HashSet<string>();
			while(iter.MoveNext())
			{
				ResNode2 node = iter.Current.Value;
				assetNamesList.Clear();
				if (tryFixSameFilenameHashCollision)					
					ResNode2.GetBundleNecessaryAssetNameList(node, assetNamesList, reverseGroupMap, node.path, 
						BundleResourceFacadeImpl.PROJECT_DYNAMIC_RESOURCE_ROOT);
				else
					ResNode2.GetAllGroupPath(node, assetNamesList);
				
				EditorAssetNode assetNode = assetNodeMap[node.path];
				AssetBundleBuild build = new AssetBundleBuild();
				build.assetBundleName = assetNode.id;
				build.assetBundleVariant = BUNDLE_VARIANT_NAME;
				build.assetNames = assetNamesList.ToArray();

				if (assetNode.needExportBundle)
					bundleBuildList.Add(build);
				else
					rawBuildList.Add(build);
			}

			BuildContext context = new BuildContext();
			context.bundleBuildList = bundleBuildList.ToArray();
			context.rawBuildList = rawBuildList.ToArray();

			return context;
		}

		/// <summary>
		/// 使用BuildMap的方式打包，即传入所有要打的AB包，让Unity自己计算依赖，和是否需要重新打包。
		/// </summary>
		/// <param name="context"></param>
		/// <param name="outputRoot"></param>
		/// <param name="target"></param>
		/// <param name="options"></param>
		/// <returns></returns>
		private static bool ExportBundle(BuildContext context, string outputRoot, BuildTarget target, BuildAssetBundleOptions options)
		{
			string absTempExportBundleRoot = Path.GetFullPath(outputRoot);
			
			// rebuild时清空目录，防止历史残留
			if ((options & BuildAssetBundleOptions.ForceRebuildAssetBundle) != 0)
				Directory.Delete(absTempExportBundleRoot, true);
			if (!Directory.Exists(absTempExportBundleRoot))
				Directory.CreateDirectory(absTempExportBundleRoot);

			// 无需以bundle包装的文件直接copy出去，如音视频
			foreach (var rawBuild in context.rawBuildList)
			{
				string destPath = Path.Combine(absTempExportBundleRoot, $"{rawBuild.assetBundleName}.{rawBuild.assetBundleVariant}");
				EditorCheckDirectory(destPath);
				File.Copy(rawBuild.assetNames[0], destPath, true);
			}

			// 开BuildAssetBundleOptions.StrictMode时有错误才会返回null，不然有错也是非null
			var manifest = BuildPipeline.BuildAssetBundles(outputRoot, context.bundleBuildList, options, target);
			// 失败需要终止构建过程，提前暴露问题。比如http://git2.rivergame.net/fibmatrixframework/fibmatrix.unity/issues/5
			if (manifest == null)
			{
				Debug.LogError("build AB error in strict mode, see message above, stop whole build process");
				return false;
			}
			Debug.Log($"ab count:{manifest.GetAllAssetBundles().Length}");
			return true;
		}

		/// <summary>
		/// 将bundle合并成大文件，生成索引
		/// </summary>
		/// <param name="bundleSrc"></param>
		/// <param name="packageBundleDst"></param>
		/// <param name="originalPackageRoot"></param>
		/// <param name="remoteBundleDst"></param>
		/// <param name="assetMap"></param>
		/// <returns></returns>
		private static IEnumerator FillNodeInfoAndFlattenDirectory(string bundleSrc, string packageBundleDst, 
				string originalPackageRoot, string remoteBundleDst, Dictionary<string, EditorAssetNode> assetMap)
		{
			string absBundleSrc = Path.GetFullPath(bundleSrc);
			string absPackageBundleDst = Path.GetFullPath(packageBundleDst);
			string absOriginalPackageDst = Path.GetFullPath(originalPackageRoot);
			string absRemoteBundleDst = Path.GetFullPath(remoteBundleDst);
			Debug.Log("FlattenDirectory src: " + absBundleSrc);
			Debug.Log("FlattenDirectory package dst: " + absPackageBundleDst);
			Debug.Log("FlattenDirectory original package dst: " + absOriginalPackageDst);
			Debug.Log("FlattenDirectory cdn dst: " + absRemoteBundleDst);
			if(!Directory.Exists(absPackageBundleDst))
				Directory.CreateDirectory(absPackageBundleDst);
			if (!Directory.Exists(absOriginalPackageDst))
				Directory.CreateDirectory(absOriginalPackageDst);
			if(!Directory.Exists(absRemoteBundleDst))
				Directory.CreateDirectory(absRemoteBundleDst);

			List<string> fragmentRelativePathList = new List<string>(); 
			Dictionary<string, long> packageBundleOffsetMap = new Dictionary<string, long>();
			int curFragmentIndex = -1;
			long curFragmentSize = int.MaxValue;
			FileStream fs = null;
			int count = 0;
			foreach(string path in assetMap.Keys)
			{
				AssetNode node = assetMap[path];
				string srcPath = $"{absBundleSrc}/{node.id.ToLower()}.{BUNDLE_VARIANT_NAME}";
				string extension = (node as EditorAssetNode).extension;
				//                Debug.Log("GetFileName: " + fileName);
				if(File.Exists(srcPath))
				{
					FileInfo fileInfo = new FileInfo(srcPath);
					node.crc32 = Crc32.ComputeFile(srcPath);
					node.md5 = CommonUtil.GetFileMD5(srcPath);
					node.size = (ulong)fileInfo.Length;
					var exportName = (node as EditorAssetNode).finalExportName;
					node.url = string.Format("{0}/{1}{2}", BUNDLE_FLATTEN_DIRECTORY, exportName, extension);
					string exportRemotePath = string.Format("{0}/{1}_{2}{3}", absRemoteBundleDst, exportName, node.md5, extension);
					string exportPackagePath = string.Format("{0}/{1}{2}", absPackageBundleDst, exportName, extension);

					byte[] srcBytes = null;
					if (!(node as EditorAssetNode).dynamic)
					{
						if (_packageAssetMode != PackageAssetMode.Combine)
						{
							packageBundleOffsetMap.Add(node.url, 0);
							File.Copy(srcPath, exportPackagePath, true);
						}
						else
						{
							if (!(node as EditorAssetNode).needExportBundle)
							{
								packageBundleOffsetMap.Add(node.url, -1);
								File.Copy(srcPath, exportPackagePath, true);
							}
							else
							{
								if (curFragmentSize + fileInfo.Length > MAX_FRAGMENT_SIZE)
								{
									if (fs != null)
									{
										fs.Flush(true);
										fs.Close();
										fs.Dispose();
									}

									curFragmentSize = 0;
									++curFragmentIndex;
									string fragmentName = $"{FRAGMENT_FLATTEN_NAME}{curFragmentIndex}{AssetConfig.ASSETBUNDLE_EXT_NAME}";
									string fragmentRelativePath = $"{BUNDLE_FLATTEN_DIRECTORY}/{fragmentName}";
									fragmentRelativePathList.Add(fragmentRelativePath);
									fs = File.OpenWrite($"{absPackageBundleDst}/{fragmentName}");
								}

								srcBytes = File.ReadAllBytes(srcPath);
								fs.Write(srcBytes, 0, srcBytes.Length);
								packageBundleOffsetMap.Add(node.url, ((long) curFragmentIndex << 32) | curFragmentSize);
								curFragmentSize += srcBytes.Length;
							}
						}
					}

					if (srcBytes == null)
					{
						File.Copy(srcPath, exportRemotePath, true);
					}
					else
					{
						File.WriteAllBytes(exportRemotePath, srcBytes);
					}

					count++;
				}
				else
				{
					Debug.LogError("Error: Flatten file not exist: " + srcPath);
					DoOnFail();
				}

				// yield return 0;
			}
			if (fs != null)
			{
				fs.Flush(true);
				fs.Close();
				fs.Dispose();
			}

			using (FileStream otfs = File.OpenWrite($"{absOriginalPackageDst}/{BundleResourceFacadeImpl.BUNDLE_OFFSET_TABLE_FILE}"))
			using (BinaryWriter otbw = new BinaryWriter(otfs))
			{
				otbw.Write(fragmentRelativePathList.Count);
				foreach (var fpath in fragmentRelativePathList)
				{
					otbw.Write(fpath);
				}

				var tableBytes = BuildAssetOffsetFile(packageBundleOffsetMap);
				otbw.Write(tableBytes.Length);
				otbw.Write(tableBytes, 0, tableBytes.Length);
				otbw.Flush();
			}	
			Debug.Log("Flatten asset count: " + count);
			yield break;
		}
		
		[MenuItem("FMFramework/Debug/生成Debug脚本工程")]
		private static void GenerateScriptDebugProject()
		{
			List<string> allSourcePath = new List<string>();
			List<string> allProjPath = new List<string>();
			string genPath = "Temp" + Path.AltDirectorySeparatorChar + "DebugScriptProj" + Path.AltDirectorySeparatorChar;
			EditorCheckDirectory(genPath, false);
			CsSolutionScriptObject gameSln = GameProjGenerator.GenerateGameProjects(genPath, "GameProj", false,
				AssetBuilderCfg.Instance.gameSolutionAssetPath, allSourcePath, allProjPath, null, GameProjGenerator.ProjectOutputMode.DebugClient);
		}

		private static void TestCompileGameMainProj()
		{
			if (Directory.Exists(PACKAGE_FINAL_EXPORT_ROOT))
			{
				Directory.Delete(PACKAGE_FINAL_EXPORT_ROOT, true);
			}
			EditorCheckDirectory(PACKAGE_FINAL_EXPORT_BUNDLE_ROOT, false);
			
			if (Directory.Exists(HOTUPDATE_FINAL_EXPORT_ROOT))
			{
				Directory.Delete(HOTUPDATE_FINAL_EXPORT_ROOT, true);
			}
			Directory.CreateDirectory(HOTUPDATE_FINAL_EXPORT_BUNDLE_ROOT);
			
			if (!Directory.Exists(TEMP_EXPORT_HOTUPDATE_BUNDLE_ROOT))
			{
				Directory.CreateDirectory(TEMP_EXPORT_HOTUPDATE_BUNDLE_ROOT);
			}
			List<EditorAssetNode> nodes = CompileGameMainProj(PackMode.Full, EditorUserBuildSettings.development, null);
			for (int i = 0; i < nodes.Count; i++)
			{
				Debug.Log(EditorJsonUtility.ToJson(nodes[i]));
			}
		}
		
		private static List<string> GetAOTAssemblyList(string platformName, CsSolutionScriptObject gameSln, string bclTopDirFullPath, List<string> needCopyDlls)
		{
			CsSolutionScriptObject.PlatformConfig config = null;
			if (platformName == "Android")
			{
				config = gameSln.android;
			}
			else if (platformName == "iOS")
			{
				config = gameSln.iOS;
			}

			if (config == null || config.aotMode == CsSolutionScriptObject.AotMode.None)
			{
				Debug.Log("config is empty or aot mode is none");
				return null;
			}
			
			HashSet<string> extraIncludeList = new HashSet<string>(config.otherAotModules);
			List<string> aotList = null;
			
			Debug.Log("BCL Top Dir " + bclTopDirFullPath);

			foreach (var dllPath in needCopyDlls)
			{
				if (dllPath.StartsWith(bclTopDirFullPath))
				{
					if (config.bclFullAot)
					{
						aotList = aotList ?? new List<string>();
						aotList.Add(dllPath);
					}
				}
				else
				{
					string moduleName = Path.GetFileNameWithoutExtension(dllPath);
					if (extraIncludeList.Contains(moduleName))
					{
						aotList = aotList ?? new List<string>();
						aotList.Add(dllPath);
					}
					else
					{
						Debug.Log("Skip " + dllPath);	
					}
				}
			}

			return aotList;
		}

		public static bool CheckMonoAOTCompilerInstalling(string platformName, List<string> archList)
		{
			foreach (var arch in archList)
			{
				string aotCompilerZipPath = FM_MonoEditorConst.externalFullPath + "/Runtime/mono/" +
				                        platformName.ToLower() + "_" + arch + "/aot-compiler.zip";
				string libraryFM_MonoDir = "./Library/FM_Mono";
				string aotCompilerExtractParent = libraryFM_MonoDir + "/" + platformName.ToLower() + "_" + arch;
				string aotCompilerExtractPath = aotCompilerExtractParent + "/aot-compiler";
				
				if (!Directory.Exists(aotCompilerExtractPath))
				{
				
#if UNITY_EDITOR_WIN  
	                Debug.LogError("不支持Windows上进行AOT构建");
	                return false;
#else
					if (!Directory.Exists(libraryFM_MonoDir))
					{
						Directory.CreateDirectory(libraryFM_MonoDir);
					}
					if (!Directory.Exists(aotCompilerExtractParent))
					{
						Directory.CreateDirectory(aotCompilerExtractParent);
					}
					if (!CommandLineUtils.Bash($"unzip -d {aotCompilerExtractParent} {aotCompilerZipPath}", out var result))
					{
						Debug.LogError($"解压缩{platformName}.{arch}AOT编译器失败");
						Debug.LogError(result);
						return false;
					}
#endif
				}
			}

			return true;
		}
		
		private static List<EditorAssetNode> CompileGameMainProj(PackMode packMode, bool isDebug, string skipUpdateDllList)
		{
			HashSet<string> forceDisableUpdateSet = new HashSet<string>();
			if (!string.IsNullOrEmpty(skipUpdateDllList) && File.Exists(skipUpdateDllList))
			{
				string[] forceDisableUpdateRaw = File.ReadAllLines(skipUpdateDllList);
				foreach(var a in forceDisableUpdateRaw)
				{
					forceDisableUpdateSet.Add(Path.GetFullPath(a));
				}
			}
			EditorCheckDirectory(TEMP_EXPORT_ASSEMBLIES_ROOT + "/blank", false);
			string platformName;
			switch (EditorUserBuildSettings.activeBuildTarget)
			{
			case BuildTarget.Android:
				platformName = "Android";
				break;
			case BuildTarget.iOS:
				platformName = "iOS";
				break;
			default:
				platformName = "Editor";
				break;
			}
			
			List<string> allSourcePath = new List<string>();
			List<string> allProjPath = new List<string>();
			string genPath = TEMP_EXPORT_ASSEMBLIES_ROOT + Path.AltDirectorySeparatorChar;
			CsSolutionScriptObject gameSln = GameProjGenerator.GenerateGameProjects(genPath, "GameProj", false, 
				AssetBuilderCfg.Instance.gameSolutionAssetPath, allSourcePath, allProjPath, null, isDebug ? GameProjGenerator.ProjectOutputMode.DebugSource : GameProjGenerator.ProjectOutputMode.Normal);
			const string OUTPUT_FOLDER = "Output";
			string BCL_TOP_DIR = FM_MonoEditorConst.externalFullPath + "/Runtime/bcl";
			string outputPath = genPath + OUTPUT_FOLDER;
			CommonUtil.DeleteDir(outputPath);
			string bclDir = Path.Combine(BCL_TOP_DIR, platformName.ToLower());
			
			File.Copy(FM_MonoEditorConst.buildPropsFullPath, genPath + "Directory.Build.props", true);
			
			bool result = false;
			if (gameSln != null)
			{
				string slnPath = genPath + Path.AltDirectorySeparatorChar + "GameProj.sln";
				result = GameProjGenerator.CompileGameProject(slnPath, isDebug, platformName, OUTPUT_FOLDER, false);
			}

			if (!result)
			{
				Debug.LogError("Compile game project failed..");
				DoOnFail();
				return null;
			}


			string outputFullPath = Path.GetFullPath(outputPath);
			string bclDirFullPath = Path.GetFullPath(bclDir);

			List<string> needCopyDlls = null; 
			List<string> archList = new List<string>();
				
			if (platformName != "Editor")
			{
				string stdOut;
				string stdError;
				int exitCode = 0;
				if (Utils.CheckMonoInstalling())
				{
#if UNITY_EDITOR_WIN
					exitCode = CommandLineUtils.ExecuteCommand(FM_MonoEditorConst.externalFullPath + "/Tools/AssemblyRefResolver.exe",
#else              
					exitCode = CommandLineUtils.ExecuteCommand(FM_MonoEditorConst.monoPath,
#endif
						new string[]
						{
#if !UNITY_EDITOR_WIN
							FM_MonoEditorConst.externalFullPath + "/Tools/AssemblyRefResolver.exe",
#endif
							outputFullPath,
							bclDirFullPath
						}, ".", false,
						out stdOut,
						out stdError);
				}
				else
				{
					stdOut = "";
					stdError = "";
					exitCode = 1;
				}

				if (exitCode != 0)
				{
					Debug.LogError(stdError);
					DoOnFail();
					return null;
				}

				if (!string.IsNullOrEmpty(stdError))
				{
					Debug.LogError(stdError);
				}
				Debug.Log("Find all need dlls: " + stdOut);
				needCopyDlls = stdOut.Split(new char []{';'}).Select(p=>p.Trim()).ToList();
				if (platformName == "Android")
				{
					string monoAndroidDllPath = Path.Combine(bclDirFullPath, "Mono.Android.dll");
					if (!needCopyDlls.Contains(monoAndroidDllPath))
					{
						needCopyDlls.Add(monoAndroidDllPath);
					}
					archList.Add("armeabi-v7a");
					archList.Add("arm64-v8a");
				}
				else if (platformName == "iOS")
				{
					archList.Add("arm64");
				}

			}
			else
			{
				needCopyDlls = new List<string>(Directory.GetFiles(outputFullPath, "*.dll"));
			}
			
			List<string> aotDllPathList = GetAOTAssemblyList(platformName, gameSln, Path.GetFullPath(BCL_TOP_DIR), needCopyDlls);

			if (aotDllPathList != null && aotDllPathList.Count > 0)
			{
				Debug.Log("AOT Assemblies " + string.Join(";", aotDllPathList));
				string stdOut;
				string stdError;
				// string tempOutPath = Path.Combine(Path.GetTempPath(), "AOTOutput");
				if (!CheckMonoAOTCompilerInstalling(platformName, archList))
				{
					DoOnFail();
					return null;
				}
				int exitCode = CommandLineUtils.ExecuteCommand(FM_MonoEditorConst.monoPath,
					new string[] {FM_MonoEditorConst.externalFullPath + "/Tools/AotExporter.exe", "game", "./Library/FM_Mono", bclDirFullPath, /*tempOutPath*/"Assets/Plugins/" + platformName, platformName.ToLower(), string.Join(",", archList), "false", string.Join(";", aotDllPathList), "false"}, ".", false,
					out stdOut,
					out stdError);
				Debug.Log("Std Output:" + stdOut);
				Debug.Log("Std Error:" + stdError);
				if (exitCode != 0)
				{
					Debug.LogError(stdError);
					DoOnFail();

					return null;
				}
				else
				{
					// string platformOutputPath = Path.Combine(tempOutPath, platformName);
					// if (Directory.Exists(platformOutputPath))
					// {
					// 	string finalOutputPath = Path.Combine("Assets/Plugins", platformName);
					// 	if (!Directory.Exists(finalOutputPath))
					// 	{
					// 		Directory.CreateDirectory(finalOutputPath);
					// 	}
					// 	Directory.
					// }
					Debug.Log("AOT Complete...");
				}
			}

			
			List<EditorAssetNode> updateScriptNodes = new List<EditorAssetNode>();
			foreach (var dllPath in needCopyDlls)
			{
				byte[] originalBytes = File.ReadAllBytes(dllPath);
				string originalFileName = Path.GetFileName(dllPath);
				string fileNameWithoutExt = Path.GetFileNameWithoutExtension(dllPath);
				string ext = _encrypt ? ".mdl" : ".dll";
				string finalFileName = fileNameWithoutExt + ext;
				if (_encrypt)
				{
					int nameLen = originalFileName.Length;
					for (int i = 0; i < nameLen; ++i)
					{
						if (i < originalBytes.Length)
						{
							//				Debug.Log(string.Format("dll[{0}] = {1}", i, dllbytes[i]));
							originalBytes[i] = (byte) (originalBytes[i] ^ (byte) nameLen);
						}
					}
				}

				string destPath = null;
				if ((packMode & PackMode.Package) != 0)
				{
					destPath = Path.Combine(PACKAGE_FINAL_EXPORT_ASSEMBLY_ROOT, finalFileName);
					EditorCheckDirectory(destPath);
					File.WriteAllBytes(destPath, originalBytes);
				}

				string fullDllPath = Path.GetFullPath(dllPath);
				if (fullDllPath.StartsWith(bclDirFullPath) || forceDisableUpdateSet.Contains(fullDllPath))
				{
					continue;
				}
				
				var node = new EditorAssetNode();
				node.md5 = CommonUtil.GetBytesMD5(originalBytes);
				
				destPath = string.Format("{0}/{1}_{2}{3}", HOTUPDATE_FINAL_EXPORT_ASSEMBLY_ROOT, fileNameWithoutExt, node.md5, ext);
				
				byte[] compressedBytes = null;
				using (MemoryStream outStream = new MemoryStream())
				{
					outStream.SetLength(0);
					LZ4Stream cs = new LZ4Stream(outStream, LZ4StreamMode.Compress);
					cs.Write(originalBytes, 0, originalBytes.Length);
					cs.Flush();
					compressedBytes = outStream.ToArray();
					node.compressedSize = (ulong)outStream.Length;
				}
				node.crc32 = Crc32.Compute(compressedBytes);

				if ((packMode & PackMode.HotUpdate) != 0)
				{
					EditorCheckDirectory(destPath);
					File.WriteAllBytes(destPath, compressedBytes);
				}

				FileInfo fi = new FileInfo(dllPath);
				// fi.LastAccessTimeUtc = new DateTime(1970, 1, 1, 0, 0, 0, 0);
				// fi.LastWriteTimeUtc = new DateTime(1970, 1, 1, 0, 0, 0, 0);
				node.id = finalFileName;
				node.size = (ulong) fi.Length;
				node.url = string.Format("{0}/{1}",
					Path.GetFileNameWithoutExtension(PACKAGE_FINAL_EXPORT_ASSEMBLY_ROOT), finalFileName);
				updateScriptNodes.Add(node);
			}

			return updateScriptNodes;
			// 	{
			// 		FileInfo fi = new FileInfo(dllPath);
			// 		string dllName = Path.GetFileName(dllPath);
			// 		var node = new EditorAssetNode();
			//
			// 		node.id = dllName;
			// 		node.dynamic = false;
			// 		node.size = (ulong)fi.Length;
			// 		
			// 		int nameLen = newName.Length;
			// 		for (int i = 0; i < nameLen; ++i)
			// 		{
			// 			if (i < dllbytes.Length)
			// 			{
			// 				//				Debug.Log(string.Format("dll[{0}] = {1}", i, dllbytes[i]));
			// 				dllbytes[i] = (byte)(dllbytes[i] ^ (byte)nameLen);
			// 			}
			// 		}
			// 		string newDllPath = "assets/Mdls/" + newNameWithExt;
			// 		File.WriteAllBytes("Build/Temp/" + newDllPath, dllbytes);
			// 		ExecuteCommand("zip", "-g " + _apkFilePath + " " + newDllPath);
			// 		using (FileStream outStream = new FileStream(PLATFORM_EXPORT_MDL_ROOT + "/" + newNameWithExt, FileMode.OpenOrCreate))
			// 		{
			// 			outStream.SetLength(0);
			// 			LZ4Stream cs = new LZ4Stream(outStream, LZ4StreamMode.Compress);
			// 			cs.Write(dllbytes, 0, dllbytes.Length);
			// 			cs.Flush();
			// 			node.compressedSize = outStream.Length;
			// 		}
			//
			// 		node.crc32 = Crc32.Compute(File.ReadAllBytes(PLATFORM_EXPORT_MDL_ROOT + "/" + newNameWithExt));
			//
			// 	}
		}
		
		// private static void AddAllReferenceAssembly(System.Reflection.Assembly assembly,
		// 	Dictionary<string, string> allDllPathMap, HashSet<string> collectDlls)
		// {
		// 	collectDlls.Add(assembly.GetName().Name);
		// 	assembly.GetReferencedAssemblies()
		// }

		// private static void CommitFilesToSVN(string bundleFolder, string compressedFolder, string targetFolder, string platformName, Dictionary<string, EditorAssetNode> assetMap)
		// {
		// 	// VCCommands vcc = VCCommands.Instance;
		// 	string prefix = "PlatformResources/" + platformName + "/";
		// 	StringBuilder commitSh = new StringBuilder();
		// 	commitSh.AppendLine("svn st --username 'lichenguang' --password 'Elex%Proj5858' " + bundleFolder + " | awk -F '       ' '$1 == \"?\" { print $2 }' | xargs -I% svn add %");
		// 	commitSh.AppendLine("svn st --username 'lichenguang' --password 'Elex%Proj5858' " + bundleFolder + " | awk -F '       ' '$1 == \"!\" { print $2 }' | xargs -I% svn delete %");
		// 	commitSh.AppendLine("svn commit --username 'lichenguang' --password 'Elex%Proj5858' " + bundleFolder + " -m \"deploy assets\"");
		// 	commitSh.AppendLine("for file in " + bundleFolder + "/*.bytes; do   rev=`svn info \"$file\" | sed -ne 's/^Last Changed Rev: //p'`;   echo \"$file|$rev\">>rev.tmp; done");
		// 	if (File.Exists("rev.tmp"))
		// 	{
		// 		File.Delete("rev.tmp");
		// 	}
		// 	File.WriteAllText("commitSh.sh", commitSh.ToString());
		// 	ExecuteCommand("sh", "commitSh.sh");
		// 	Dictionary<string, string> revMap = new Dictionary<string, string>();
		// 	if (File.Exists("rev.tmp"))
		// 	{
		// 		string[] revRaws = File.ReadAllLines("rev.tmp");
		// 		foreach (var item in revRaws)
		// 		{
		// 			string[] revs = item.Split(CommonUtil.lineSeparator);
		// 			Debug.Log(item);
		// 			revMap.Add(revs[0], revs.Length > 1 ? revs[1] : "");
		// 		}
		// 	}
		// 	
		// 	// byte[] assetCompareMapBytes = File.Exists(prefix + "Config/Assets.bytes-8496") ? File.ReadAllBytes(prefix + "Config/Assets.bytes-8496") : null;
		// 	// Dictionary<string, AssetNode> lastAssetCompareMap = null;
		// 	// if (assetCompareMapBytes != null)
		// 	// {
		// 	// 	AssetList list =
		// 	// 			ConfigFormatter.DeserializeAssetsList(assetCompareMapBytes, JsonUtility.FromJson<AssetList>);
		// 	// 	AssetNode[] nodes = list != null ? list.nodes : null;
		//
		// 	// 	if (nodes != null)
		// 	// 	{
		// 	// 		lastAssetCompareMap = new Dictionary<string, AssetNode>();
		// 	// 		foreach (var lastNode in nodes)
		// 	// 		{
		// 	// 			lastAssetCompareMap.Add(lastNode.id, lastNode);
		// 	// 		}
		// 	// 	}
		// 	// }
		// 	// List<string> precommitList = new List<string>();
		// 	Dictionary<string, AssetNode> assetPathMap = new Dictionary<string, AssetNode>();
		// 	
		// 	Dictionary<string, AssetNode> curAssetCompareMap = new Dictionary<string, AssetNode>();
		//
		// 	foreach (string path in assetMap.Keys)
		// 	{
		// 		AssetNode node = assetMap[path];
		// 		if (curAssetCompareMap.ContainsKey(node.id))
		// 		{
		// 			Debug.Log("Error: commit duplicated " + node.id + " url: " + node.url);
		// 			continue;	
		// 		}
		// 		else
		// 		{
		// 			curAssetCompareMap.Add(node.id, node);
		// 		}
		// 		string fileName = Path.GetFileName(path);
		// 		string assetPathInBundle = string.Format("{0}/{1}.bytes", bundleFolder, fileName);
		// 		node.url = string.Format("Bundles/{0}.bytes", fileName);
		//
		// 		string version = null;//vcc.GetRevision(assetPathInBundle.Contains("@") ? assetPathInBundle + "@" : assetPathInBundle,
		// 		revMap.TryGetValue(assetPathInBundle, out version);
		// 		// 	true);
		// 		if (version == null)
		// 		{
		// 			Debug.Log("Error: Some asset has no version : " + assetPathInBundle);
		// 			EditorApplication.UnlockReloadAssemblies();
		// 			if (_isBatchMode)
		// 			{
		// 				EditorApplication.Exit(1);
		// 			}
		// 		}
		// 		node.version = version;
		//
		// 		if (assetPathMap.ContainsKey(assetPathInBundle))
		// 		{
		// 			Debug.Log("Error: commit duplicated " + node.id + " url: " + node.url);
		// 			continue;
		// 		}
		//
		// 		if (!File.Exists(assetPathInBundle))
		// 		{
		// 			Debug.Log("Error: file not exist " + node.id + " url: " + node.url);
		// 			continue;
		// 		}
		// 		//把PlatformResource/Android/CompressedBundles 里的所有bundle文件（*.bytes）重命名为*.version.bytes，放到PlatformResource/temp/CompressedBundles 里，
		// 		//这里的version是这个bundle被提交时的版本号（如果不是上一次提交，则使用最近一次提交的版本号）
		// 		string copyPath = assetPathInBundle.Replace(bundleFolder, targetFolder);
		// 		int suffixIndex = copyPath.LastIndexOf(".");
		// 		if (suffixIndex == -1)
		// 		{
		// 			Debug.Log("Can't find suffix " + copyPath);
		// 			continue;
		// 		}
		// 		copyPath = copyPath.Insert(suffixIndex, "." + version);
		// 		copyPath = Application.dataPath.Replace("Assets", copyPath);
		// 		EditorCheckDirectory(copyPath);
		// 		string assetPathInCompressed = assetPathInBundle.Replace("/Bundles/", "/CompressedBundles/");
		// 		File.Copy(assetPathInCompressed, copyPath, true);
		// 	}
		//
		// 	Debug.Log("commit bundles completed");
		// 	
		// 	// CompareCrc32AndFixAssetMap(curAssetCompareMap, lastAssetCompareMap);
		// 	CommitAssetMap(platformName, assetMap);
		// 	//            LogAssetMap(assetMap);
		// }
		//
		// private static void CompareCrc32AndFixAssetMap(Dictionary<string, AssetNode> curMap,
		//                                                Dictionary<string, AssetNode> lastMap)
		// {
		// 	if (lastMap == null)
		// 	{
		// 		return;
		// 	}
		//
		// 	foreach (var node in curMap)
		// 	{
		// 		if (lastMap.ContainsKey(node.Key))
		// 		{
		// 			AssetNode lastNode = lastMap[node.Key];
		// 			if (lastNode.crc32 == node.Value.crc32 && lastNode.compressedSize == node.Value.compressedSize && lastNode.uncompressedSize == node.Value.uncompressedSize && node.Value.compressed == lastNode.compressed && lastNode.version != node.Value.version)
		// 			{
		// 				node.Value.version = lastNode.version;
		// 				Debug.Log("Fix " + node.Key + " version to " + node.Value.version);
		// 			}
		// 		}
		// 	}
		// }
		//
		// private static void CommitAssetMap(string platformName, Dictionary<string, EditorAssetNode> assetMap)
		// {
		// 	string absPrefix = Application.dataPath.Replace("Assets", "PlatformResources/" + platformName);
		//
		// 	string assetConfigPath = absPrefix + "/Config/Assets.bytes";
		// 	EditorCheckDirectory(assetConfigPath);
		// 	using (
		// 		FileStream fileStream = new FileStream(assetConfigPath, FileMode.OpenOrCreate)
		// 	)
		// 	{
		// 		ConfigFormatter.SerializeAssetsList(fileStream, EditorAssetNode.CreateAssetListFrom(assetMap), JsonUtility.ToJson);
		// 	}
		// 	Debug.Log("Assets.bytes generated");
		//
		// 	AssetDatabase.Refresh();
		//
		// 	StringBuilder commitSh = new StringBuilder();
		// 	commitSh.AppendLine("svn st --username 'lichenguang' --password 'Elex%Proj5858' " + "PlatformResources/" + platformName + "/Config/Assets.bytes" + " | awk '$1 == \"?\" { print $2 }' | xargs -I% svn add %");
		// 	commitSh.AppendLine("svn commit --username 'lichenguang' --password 'Elex%Proj5858' " + "PlatformResources/" + platformName + "/Config/Assets.bytes -m \"deploy assets\"");
		// 	commitSh.AppendLine("svn info --username 'lichenguang' --password 'Elex%Proj5858' " + "PlatformResources/" + platformName + "/Config/Assets.bytes" + " | sed -ne 's/^Last Changed Rev: //p' >assetVer.tmp");
		// 	File.WriteAllText("commitSh.sh", commitSh.ToString());
		// 	ExecuteCommand("sh", "commitSh.sh");
		// 	string assetVer = File.Exists("assetVer.tmp") ? File.ReadAllText("assetVer.tmp") : null;
		// 	if (!string.IsNullOrEmpty(assetVer))
		// 	{
		// 		assetVer = assetVer.Trim();
		// 		EditorCheckDirectory(Application.dataPath.Replace("Assets", "PlatformResources") + "/temp/Config/Assets." +
		// 								assetVer + ".bytes");
		// 		File.Copy(assetConfigPath, Application.dataPath.Replace("Assets", "PlatformResources") + "/temp/Config/Assets." + assetVer + ".bytes", true);
		// 		File.WriteAllText(Application.dataPath.Replace("Assets", "PlatformResources") + "/temp/Config/Assets_Version", assetVer);
		// 		Debug.Log("deploy successful");
		// 		// VCSettings.VCEnabled = false;
		// 		EditorApplication.UnlockReloadAssemblies();
		// 		if (_isBatchMode)
		// 		{
		// 			EditorApplication.Exit(0);
		// 		}
		// 	}
		// 	else
		// 	{
		// 		EditorApplication.UnlockReloadAssemblies();
		// 		if (_isBatchMode)
		// 		{
		// 			EditorApplication.Exit(1);
		// 		}
		// 	}
		// }
		//
		// private static void RefreshStatus(Action<bool> taskCompleteCallback)
		// {
		// 	VCCommands vcc = VCCommands.Instance;
		// 	EditorUtility.DisplayProgressBar("正在准备提交", "正在刷新文件状态", 0);
		// 	bool remoteProjectReflection = VCSettings.ProjectReflectionMode == VCSettings.EReflectionLevel.Remote;
		// 	vcc.DeactivateRefreshLoop();
		// 	vcc.ClearDatabase();
		// 	var statusLevel = remoteProjectReflection ? StatusLevel.Remote : StatusLevel.Local;
		// 	var detailLevel = remoteProjectReflection ? DetailLevel.Verbose : DetailLevel.Normal;
		// 	VCCommands.Instance.StatusTask(statusLevel, detailLevel).ContinueWithOnNextUpdate(s =>
		// 		{
		// 			vcc.ActivateRefreshLoop();
		// 			taskCompleteCallback(s);
		// 		});
		//
		// }
		//
		// private static void OnProgressInfomation(string s)
		// {
		// 	EditorUtility.DisplayProgressBar("正在提交", s, 0);
		// }
		//
		// private static IEnumerator WaitForCommitTaskComplete(VCCommands vcc, List<string> commitList, Action<bool> commitCompleteCallback)
		// {
		// 	int totalLen = 0;
		// 	List<List<string>> commitLists = new List<List<string>>();
		// 	List<string> splitCommitList = new List<string>();
		// 	commitLists.Add(splitCommitList);
		// 	for (int i = 0; i < commitList.Count; ++i)
		// 	{
		// 		totalLen += commitList [i].Length;
		// 		if (totalLen > 8191)
		// 		{
		// 			splitCommitList = new List<string>();
		// 			commitLists.Add(splitCommitList);
		// 			totalLen = commitList [i].Length;
		// 		}
		// 		splitCommitList.Add(commitList [i]);
		// 	}
		//
		// 	bool commitResult = true;
		// 	for (int i = 0; i < commitLists.Count; ++i)
		// 	{
		// 		//BUG：父文件夹Parent下面有两个子文件夹A和B
		// 		//A、B都没有Add过，但只想Add并Commit A
		// 		//调用CommitTask(A)，会把A和B都一起Add+Commit，解决方式是先手动Commit一个空的A文件夹，然后再调用CommitTask(A);
		// 		IAsyncResult commitTask = vcc.CommitTask(commitLists [i], "deploy assets");
		// 		while (!commitTask.IsCompleted)
		// 		{
		// 			yield return 0;
		// 		}
		// 		object taskObj = commitTask;
		// 		bool taskResult = (bool)taskObj.GetType().GetProperty("Result").GetValue(taskObj, null);
		// 		commitResult = taskResult && commitResult;
		// 		if (!commitResult)
		// 		{
		// 			break;
		// 		}
		// 		yield return 0;
		// 	}
		// 	commitCompleteCallback(commitResult);
		// }
		//
		// private static void LogAssetMap(Dictionary<string, AssetNode> assetMap)
		// {
		// 	StringBuilder sb = new StringBuilder(50000);
		// 	var iter = assetMap.GetEnumerator();
		// 	while(iter.MoveNext())
		// 	{
		// 		AssetNode node = iter.Current.Value;
		// 		sb.AppendLine(string.Format("id:{0}, url:{1}, v:{2}, {3}, {4}, compressed:{5}:{6}/{7}", node.id, node.url, node.version, node.dynamic, node.extractFromPackage, 
		// 			node.compressed, node.compressedSize, node.uncompressedSize));
		// 	}
		// 	Debug.Log(sb);
		// }
	}
}